#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
#include <tchar.h>
#include "../Source_code_for_dll/GenSort.h"

#pragma comment(lib, "../Source_code_for_dll/Release/GenSort.lib")
#pragma warning(disable:4996)

#define _DEBUG

typedef struct experiment_state
{
	int halt;
	int iterations;
	int length;
} t_expstate;

const int KB_INSERT      = 82;
const int KB_DELETE      = 83;
const int KB_HOME        = 71;
const int KB_END         = 79;
const int KB_PAGE_UP     = 73;
const int KB_PAGE_DOWN   = 81;
const int KB_UP_ARROW    = 72;
const int KB_LEFT_ARROW  = 75;
const int KB_DOWN_ARROW  = 80;
const int KB_RIGHT_ARROW = 77;

const int KB_F1  = 59;
const int KB_F2  = 60;
const int KB_F3  = 61;
const int KB_F4  = 62;
const int KB_F5  = 63;
const int KB_F6  = 64;
const int KB_F7  = 65;
const int KB_F8  = 66;
const int KB_F9  = 67;
const int KB_F10 = 68;
const int KB_F11 = 133;
const int KB_F12 = 134;

const int SPEED_STOP      = 0;
const int SPEED_MIN       = -60;
const int SPEED_MAX       = 60;
const int SPEED_STEP      = 1;
const int SPEED_BIGSTEP   = 5 * SPEED_STEP;

const int DIRECTION_NORTH   = 0;
const int DIRECTION_MIN     = 0;
const int DIRECTION_MAX     = 359;
const int DIRECTION_STEP    = 1;
const int DIRECTION_BIGSTEP = 5 * DIRECTION_STEP;

int m_curSpeed     = SPEED_STOP;
int m_curDirection = DIRECTION_NORTH;

/*
	Property functions for the motor control values sent
	into the LabView program via the DLL shared data segment.
*/
bool IncrementSpeed()
{
	bool bSuccess = false;
	
	if (m_curSpeed < SPEED_MAX)
	{
		m_curSpeed += SPEED_STEP;
		bSuccess = true;
	}
	
	return bSuccess;
}

bool DecrementSpeed()
{
	bool bSuccess = false;
	
	if (m_curSpeed > SPEED_MIN)
	{
		m_curSpeed -= SPEED_STEP;
		bSuccess = true;
	}
	
	return bSuccess;
}

int GetSpeed()
{
	return m_curSpeed;
}

void Reset()
{
	m_curSpeed = SPEED_STOP;
	m_curDirection = DIRECTION_NORTH;
}

void DirectionClockwise()
{
	if (m_curDirection < (DIRECTION_MAX + DIRECTION_STEP))
	{
		m_curDirection += DIRECTION_STEP;
		if (m_curDirection > DIRECTION_MAX)
			m_curDirection -= DIRECTION_MAX;
	}
	else if (m_curDirection < DIRECTION_MAX)
	{
		m_curDirection = DIRECTION_NORTH + DIRECTION_STEP;
	}
	else
	{
		m_curDirection = DIRECTION_NORTH;
	}
}

void DirectionCounterClockwise()
{
	if (m_curDirection > (DIRECTION_MIN + DIRECTION_STEP))
	{
		m_curDirection -= DIRECTION_STEP;
	}
	else if (m_curDirection > DIRECTION_MIN)
	{
		m_curDirection = DIRECTION_MAX - DIRECTION_STEP;
	}
	else
	{
		m_curDirection = DIRECTION_MAX;
	}
}

int GetDirection()
{
	return m_curDirection;
}

unsigned long ChildExecute(TCHAR *szProgram, t_expstate* threadstate)
{
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	DWORD dwExitCode = 0;

	ZeroMemory(&si, sizeof(STARTUPINFO));
	ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
	si.cb=sizeof(STARTUPINFO);
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_SHOWNORMAL;
	CreateProcess(szProgram, _T(""), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
	WaitForInputIdle(GetCurrentProcess(), INFINITE);

	if (pi.hProcess)
	{
		SetChildStatus(true);
		dwExitCode = STILL_ACTIVE;
		while (dwExitCode == STILL_ACTIVE)
		{
		   WaitForSingleObject(pi.hProcess, 2000);
		   GetExitCodeProcess(pi.hProcess, &dwExitCode);
		   if (threadstate->halt == 1)
		   {
			   SetChildStatus(false);
		   }
		   
		   if (! ChildActive())
		   {
			   TerminateProcess(pi.hProcess, 0);
		   }
		}
	}

	return dwExitCode;
}

/*
	Thread1 is proposed run the LabView program and listen
	wheater the app is active or not.
*/
DWORD WINAPI Thread1(LPVOID lpParameter)
{
	t_expstate* threadstate = (t_expstate*)lpParameter;

	ChildExecute(_T("..\\LabVIEW_code_and_DLL\\TestEXE.exe"), threadstate);
	threadstate->halt = 1;

	return 0;
}

/*
	Thread2 is proposed to communicate with LabView via the DLL.
	The values are dummies (write-only and read-only).
*/
DWORD WINAPI Thread2(LPVOID lpParameter)
{
	t_expstate* threadstate = (t_expstate*)lpParameter;

	while (threadstate->halt == 0)
	{
		DataValueSet(DataValueGet()+1);
		/*#ifdef _DEBUG
			printf("#1 = %d, #2 = %d\n", DataValueGet(), DataValue2Get());
		#endif*/
		Sleep(250);
	}
	return 0;
}

/*
	Thread3 is proposed to control the motor control via the keyboard.
*/
DWORD WINAPI Thread3(LPVOID lpParameter)
{
	t_expstate* threadstate = (t_expstate*)lpParameter;

    int c, i;

	while (threadstate->halt == 0)
	{
		if (kbhit())
		{
			c = getch();
			if (c == 224)
			{
				c = getch();
				switch (c)
				{
					case KB_INSERT:		 break;
					
					case KB_DELETE:		 Reset();
										 CurSpeedSet(GetSpeed());
										 CurDirectionSet(GetDirection());
										 break;

					case KB_PAGE_UP:	 for (i=0; i<SPEED_BIGSTEP; i++)
										 {
											 IncrementSpeed();
										 }
										 CurSpeedSet(GetSpeed());
										 break;
				
					case KB_PAGE_DOWN:	 for (i=0; i<SPEED_BIGSTEP; i++)
										 {
											 DecrementSpeed();
										 }
										 CurSpeedSet(GetSpeed());
										 break;

					case KB_UP_ARROW:	 IncrementSpeed();
										 CurSpeedSet(GetSpeed());
										 break;
					
					case KB_DOWN_ARROW:  DecrementSpeed();
										 CurSpeedSet(GetSpeed());
										 break;

					case KB_HOME:		 for (i=0; i<DIRECTION_BIGSTEP; i++)
										 {
											 DirectionCounterClockwise();
										 }
										 CurDirectionSet(GetDirection());
										 break;

					case KB_END:		 for (i=0; i<DIRECTION_BIGSTEP; i++)
										 {
											 DirectionClockwise();
										 }
										 CurDirectionSet(GetDirection());
										 break;

					case KB_LEFT_ARROW:  DirectionCounterClockwise();
										 CurDirectionSet(GetDirection());
										 break;
					
					case KB_RIGHT_ARROW: DirectionClockwise();
										 CurDirectionSet(GetDirection());
										 break;
					
					case KB_F11:		printf("F11\n"); break;
					case KB_F12:		printf("F12\n"); break;
					
					default:			 {
											 #ifdef _DEBUG
												  printf("224: %c [%d]\n", c, (int) c);
											 #endif
										 }
										 break;
				}
			}
			else if (c == 0)
			{
				c = getch();
				switch (c)
				{
					case KB_F1:			 printf("F1\n"); break;
					case KB_F2:			 printf("F2\n"); break;
					case KB_F3:			 printf("F3\n"); break;
					case KB_F4:			 printf("F4\n"); break;
					case KB_F5:			 printf("F5\n"); break;
					case KB_F6:			 printf("F6\n"); break;
					case KB_F7:			 printf("F7\n"); break;
					case KB_F8:			 printf("F8\n"); break;
					case KB_F9:			 printf("F9\n"); break;
					case KB_F10:		 printf("F10\n"); break;
				}
			}
			else
			{
				if (c == 27)
				{
					threadstate->halt = 1;
					SetChildStatus(false);
					break;
				}
				else
				{
					#ifdef _DEBUG
						printf("CHAR: %c [%d]\n", c, (int) c);
					#endif
				}

			}
		}
		Sleep(10);
	}

	return 0;
}

void main()
{
	int argThreadCnt = 3;
	t_expstate threadstate;
	HANDLE *m_threads = (HANDLE*) calloc(argThreadCnt, sizeof(HANDLE));
	SetConsoleTitle(_T("Overkill"));

	DataValueSet(10);
	
	threadstate.halt = 0;

	m_threads[0] = CreateThread(NULL,			// default security attributes
							    0,			    // use default stack size
							    Thread1,		// 0 function
							    &threadstate,	// argument to thread function
							    CREATE_SUSPENDED,// use default creation flags
							    NULL);		    // returns the thread identifier

	m_threads[1] = CreateThread(NULL, 0, Thread2, &threadstate, CREATE_SUSPENDED, NULL);
	m_threads[2] = CreateThread(NULL, 0, Thread3, &threadstate, CREATE_SUSPENDED, NULL);
	
	// printf("Press any key to resume threads\n");
	// getch();
	
	SetThreadPriority(m_threads[0], THREAD_PRIORITY_NORMAL);
	ResumeThread(m_threads[0]);

	SetThreadPriority(m_threads[1], THREAD_PRIORITY_NORMAL);
	ResumeThread(m_threads[1]);

	SetThreadPriority(m_threads[2], THREAD_PRIORITY_NORMAL);
	ResumeThread(m_threads[2]);

	DWORD status = WaitForMultipleObjects(argThreadCnt, m_threads, TRUE, INFINITE);
    if (status == WAIT_FAILED)
    {
        printf("For some strange reason, couldn't wait for all the threads to finish.\n");
        exit(-1);
    }

	for (int i=0; i<argThreadCnt; i++)
	{
		CloseHandle(m_threads[i]);
	}
}
